
package com.ionehe.group.common.notify.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.core.type.ResolvedType;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.util.JSONPObject;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.text.DateFormat;

/**
 * Copyright (c) 2021 ionehe.com
 * Date: 2021/4/21
 * Time: 1:22 下午
 *
 * @author 2021年 <a href="mailto:a@ionehe.com">秀</a>
 * 【序列化工具】
 */
@Slf4j
public class Json {

    public static ObjectMapper nonEmpty;


    public static ObjectMapper nonDefault;


    public static ObjectMapper ignoreUnknown;


    static final ObjectMapper mapper = new ObjectMapper();
    final ObjectMapper om;
    public static final String NONE = "{}";
    public static Json JSON_NON_EMPTY_MAPPER;
    public static Json JSON_NON_DEFAULT_MAPPER;
    static final LoadingCache<Object, Type> types;
    static final LoadingCache<Object, Target> targets;

    static {
        mapper.registerModule((Module) new GuavaModule());

        ignoreUnknown = new ObjectMapper();
        ignoreUnknown.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        ignoreUnknown.registerModule((Module) new GuavaModule());

        nonEmpty = new ObjectMapper();
        nonEmpty.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        nonEmpty.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        nonEmpty.registerModule((Module) new GuavaModule());

        nonDefault = new ObjectMapper();
        nonDefault.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
        nonDefault.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        nonDefault.registerModule((Module) new GuavaModule());

        JSON_NON_EMPTY_MAPPER = new Json(nonEmpty);
        JSON_NON_DEFAULT_MAPPER = new Json(nonDefault);


        types = CacheBuilder.newBuilder().build(new TypeCacheLoader());


        targets = CacheBuilder.newBuilder().build(new TargetCacheLoader());
    }

    public Json(ObjectMapper mapper) {
        this.om = (mapper == null) ? Json.mapper : mapper;
    }

    @Deprecated
    public JavaType createCollectionType(Class<?> collectionClass, Class... elementClasses) {
        return this.om.getTypeFactory().constructParametricType(collectionClass, elementClasses);
    }

    enum Type {
        READER, INPUTSTREAM, STRING, BYTES, PARSER, FILE, URL, UNKNOWN;
    }

    public <T> String toJson(T o) {
        return of(this.om, o, "{}");
    }

    static class TypeCacheLoader extends CacheLoader<Object, Type> {
        @Override
        public Type load(Object key) throws Exception {
            if (key instanceof String) {
                return Type.STRING;
            }
            if (key instanceof InputStream) return Type.INPUTSTREAM;
            if (key instanceof Reader) return Type.READER;
            if (key instanceof byte[]) return Type.BYTES;
            if (key instanceof JsonParser) return Type.PARSER;
            if (key instanceof File)
                return Type.FILE;
            if (key instanceof URL)
                return Type.URL;
            return Type.UNKNOWN;
        }
    }

    public <T> T fromJson(Object value, Object type) {
        return from(this.om, value, type);
    }

    enum Target {
        CLASS, REFERENCE, TYPE, RESOLVED, UNKNOWN;
    }

    static class TargetCacheLoader extends CacheLoader<Object, Target> {
        @Override
        public Target load(Object key) throws Exception {
            if (key instanceof Class) return Target.CLASS;
            if (key instanceof TypeReference) return Target.REFERENCE;
            if (key instanceof JavaType) return Target.TYPE;
            if (key instanceof ResolvedType)
                return Target.RESOLVED;
            return Target.UNKNOWN;
        }
    }

    public JsonNode treeFromJson(String jsonString) throws IOException {
        return this.om.readTree(jsonString);
    }


    public Object treeToValue(JsonNode node, Class clazz) throws JsonProcessingException {
        return this.om.treeToValue((TreeNode) node, clazz);
    }

    public Object update(String jsonString, Object object) {
        try {
            return this.om.readerForUpdating(object).readValue(jsonString);
        } catch (IOException var5) {
            log.warn("update json string:" + jsonString + " to object:" + object + " error.", var5);


            return null;
        }
    }

    public String toJsonP(String functionName, Object object) {
        return toJson(new JSONPObject(functionName, object));
    }

    public void enableEnumUseToString() {
        this.om.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
        this.om.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
    }

    public void setDateFormat(DateFormat format) {
        this.om.setDateFormat(format);
    }

    public ObjectMapper getMapper() {
        return this.om;
    }

    public static void convert(Object source, Object target) throws InvocationTargetException, IllegalAccessException {
        BeanUtils.copyProperties(target, source);
    }

    public static <T> String of(ObjectMapper om, T o, String defaultvalue) {
        try {
            return (om != null) ? om.writeValueAsString(o) : mapper.writeValueAsString(o);
        } catch (JsonProcessingException e) {
            log.warn("Write object to JSON string failed: {}", (Throwable) e);

            return defaultvalue;
        }
    }

    public static <T> byte[] of(ObjectMapper om, T o) {
        try {
            return (null != om) ? om.writeValueAsBytes(o) : mapper.writeValueAsBytes(o);
        } catch (JsonProcessingException e) {
            log.warn("Write object to JSON bytes failed: {}", (Throwable) e);

            return new byte[0];
        }
    }

    public static <T> String of(T o, String defaultvalue) {
        return of(mapper, o, defaultvalue);
    }


    public static <T> byte[] of(T o) {
        return of(mapper, o);
    }


    public static <T> T from(ObjectMapper om, Object value, Object target) {
        ObjectMapper mapper = (null != om) ? om : Json.mapper;
        Type typeFrom = (Type) types.getUnchecked(value);
        Target typeTo = (Target) targets.getUnchecked(target);
        try {
            switch (typeFrom) {
                case INPUTSTREAM:
                    switch (typeTo) {
                        case CLASS:
                            return (T) mapper.readValue((InputStream) value, (Class) target);

                        case TYPE:
                            return (T) mapper.readValue((InputStream) value, (JavaType) target);

                        case REFERENCE:
                            return (T) mapper.readValue((InputStream) value, (TypeReference) target);
                    }


                case STRING:
                    switch (typeTo) {
                        case CLASS:
                            return (T) mapper.readValue((String) value, (Class) target);

                        case TYPE:
                            return (T) mapper.readValue((String) value, (JavaType) target);

                        case REFERENCE:
                            return (T) mapper.readValue((String) value, (TypeReference) target);
                    }


                case BYTES:
                    switch (typeTo) {
                        case CLASS:
                            return (T) mapper.readValue((byte[]) value, (Class) target);

                        case TYPE:
                            return (T) mapper.readValue((byte[]) value, (JavaType) target);

                        case REFERENCE:
                            return (T) mapper.readValue((byte[]) value, (TypeReference) target);
                    }


                case READER:
                    switch (typeTo) {
                        case CLASS:
                            return (T) mapper.readValue((byte[]) value, (Class) target);

                        case TYPE:
                            return (T) mapper.readValue((byte[]) value, (JavaType) target);

                        case REFERENCE:
                            return (T) mapper.readValue((byte[]) value, (TypeReference) target);
                    }


                case PARSER:
                    switch (typeTo) {
                        case CLASS:
                            return (T) mapper.readValue((JsonParser) value, (Class) target);

                        case TYPE:
                            return (T) mapper.readValue((JsonParser) value, (JavaType) target);

                        case REFERENCE:
                            return (T) mapper.readValue((JsonParser) value, (TypeReference) target);

                        case RESOLVED:
                            return (T) mapper.readValue((JsonParser) value, (ResolvedType) target);
                    }


                case FILE:
                    switch (typeTo) {
                        case CLASS:
                            return (T) mapper.readValue((File) value, (Class) target);

                        case TYPE:
                            return (T) mapper.readValue((File) value, (JavaType) target);

                        case REFERENCE:
                            return (T) mapper.readValue((File) value, (TypeReference) target);
                    }


                case URL:
                    switch (typeTo) {
                        case CLASS:
                            return (T) mapper.readValue((URL) value, (Class) target);

                        case TYPE:
                            return (T) mapper.readValue((URL) value, (JavaType) target);

                        case REFERENCE:
                            return (T) mapper.readValue((URL) value, (TypeReference) target);
                    }

                    break;
            }
        } catch (IOException e) {
            log.warn("Read object from value failed: {}/{}/{}", new Object[]{value, target, e});
        }
        return null;
    }


    public static <T> T from(Object value, Object target) {
        return from(mapper, value, target);
    }

    public static StringBuffer e(StringBuffer buf, String key, String value) {
        buf.append("\"").append(key).append("\":");
        if (null == value) {
            return buf.append("null");
        }
        return buf.append('"').append(value).append('"');
    }

    public static StringBuffer e(StringBuffer buf, String key, Object value) {
        buf.append("\"").append(key).append("\":");
        if (null == value) {
            return buf.append("null");
        }
        return buf.append(value.toString());
    }

    public static <T> T convert(ObjectMapper om, Object object, Class<T> type) {
        return (om != null) ? (T) om.convertValue(object, type) : (T) mapper.convertValue(object, type);
    }

    public static <T> T convert(Object object, Class<T> type) {
        return convert(ignoreUnknown, object, type);
    }

    public static void pretty(ObjectMapper om, Object object, OutputStream os) throws IOException {
        ObjectWriter writer = (om != null) ? om.writerWithDefaultPrettyPrinter() : mapper.writerWithDefaultPrettyPrinter();
        writer.writeValue(os, object);
    }

    public static void pretty(Object object, OutputStream os) throws IOException {
        pretty(mapper, object, os);
    }

    public static void write(ObjectMapper om, Object object, OutputStream os) throws IOException {
        if (null != om) {
            om.writeValue(os, object);
        } else {
            mapper.writeValue(os, object);
        }
    }

    public static void write(Object object, OutputStream os) throws IOException {
        write(mapper, object, os);
    }
}


